<?php
declare(strict_types=1);

namespace app\common\service;

use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\BeforeValidException;
use Firebase\JWT\ExpiredException;
use think\facade\Cache;
use DomainException;
use UnexpectedValueException;
use InvalidArgumentException;

class JwtService
{
    protected $config;
    protected $storage;

    public function __construct()
    {
        $this->config = config('jwt');
        $this->initStorage();
    }

    /**
     * 生成访问令牌
     */
    public function generateToken(int $userId, array $claims = []): string
    {
        $now = time();
        $payload = [
            'iss' => $this->config['issuer'],  // 签发者
            'iat' => $now,                    // 签发时间
            'exp' => $now + $this->config['ttl'], // 过期时间
            'sub' => $userId,                 // 用户ID
            'jti' => md5(uniqid().microtime()), // 令牌唯一标识
            'nbf' => $now,                    // 在某个时间前不可用
            'claims' => $claims               // 自定义声明
        ];

        return JWT::encode($payload, $this->config['secret'], $this->config['algo']);
    }

    /**
     * 验证访问令牌
     */
    public function verifyToken(string $token): array
    {
        try {
            $decoded = JWT::decode(
                $token, 
                new Key($this->config['secret'], $this->config['algo'])
            );
            
            // 检查黑名单
            if ($this->isBlacklisted($decoded->jti)) {
                throw new InvalidArgumentException('令牌已失效');
            }

            return (array)$decoded;

        } catch (ExpiredException $e) {
            throw new \Exception('令牌已过期', 401);
        } catch (SignatureInvalidException | BeforeValidException | DomainException | UnexpectedValueException | InvalidArgumentException $e) {
            throw new \Exception('无效令牌', 401);
        }
    }

    /**
     * 刷新令牌
     */
    public function refreshToken(string $token): string
    {
        $payload = $this->verifyToken($token);

        // 检查是否在可刷新时间窗口内
        if (time() > ($payload['exp'] + $this->config['refresh_ttl'])) {
            throw new \Exception('刷新令牌已过期', 401);
        }

        // 加入黑名单
        $this->addToBlacklist($payload['jti'], $payload['exp']);

        return $this->generateToken($payload['sub'], $payload['claims']);
    }

    /**
     * 初始化存储驱动
     */
    protected function initStorage(): void
    {
        switch ($this->config['blacklist_storage']) {
            case 'redis':
                $this->storage = Cache::store('redis');
                break;
            case 'file':
                $this->storage = Cache::store('file');
                break;
            default:
                $this->storage = Cache::instance();
        }
    }

    /**
     * 加入黑名单
     */
    public function addToBlacklist(string $jti, int $exp): void
    {
        $ttl = $exp - time();
        if ($ttl > 0) {
            $this->storage->set('jwt:blacklist:'.$jti, time(), $ttl);
        }
    }

    /**
     * 检查黑名单
     */
    public function isBlacklisted(string $jti): bool
    {
        return $this->storage->has('jwt:blacklist:'.$jti);
    }
}
